#ifndef LIGHT_SYSTEM_HPP
#define LIGHT_SYSTEM_HPP

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <unordered_set>

#include <SFML/Graphics/Shader.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/RenderTexture.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/VertexArray.hpp>

#include <SmegTools/Config.h>
#include <SmegTools/Pattern/Singleton.hpp>
#include <SmegCore/Math/BoundingVolumes/AABB.hpp>

#include "../QuadTree/QuadTree.hpp"
#include "../QuadTree/QuadTreeOccupant.hpp"

#include <SmegTools/Debug/DebugNew.hpp>

namespace smeg {
    namespace light {

class ShadowFin;
class Light;
class Hull;
class CircleHull;
class ConvexHull;

////////////////////////////////////////////////////////////
/// Represents the LightSystem
/// Based on :
/// https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows
////////////////////////////////////////////////////////////
class LightSystem : public tools::Singleton< LightSystem >
{
    DECLARE_SINGLETON( LightSystem )

    typedef std::unordered_set<Light*>                      td_LightSet;
    typedef std::unordered_set<Light*>::iterator            td_LightSetIt;

    typedef std::vector<Light*>                             td_LightVec;
    typedef std::vector<Light*>::iterator                   td_LightVecIt;

    typedef std::unordered_set<Hull*>                       td_HullSet;
    typedef std::unordered_set<Hull*>::iterator             td_HullSetIt;

public:
    ////////////////////////////////////////////////////////////
    /// Initialize the LightSystem
    ///
    /// \param _Region :        Region for the quad trees
    /// \param _pRenderWindow : Render Window
    ///
    /// \return Whether the initialization went well or not
    ///
    ////////////////////////////////////////////////////////////
    bool                        Init( const math::AABB& _Region, sf::RenderWindow* _pRenderWindow );
    ////////////////////////////////////////////////////////////
    /// Clear the scene by removing all lights and hulls
    ///
    ////////////////////////////////////////////////////////////
    void                        ClearScene();
    ////////////////////////////////////////////////////////////
    /// Compute the lights in the view
    ///
    /// For each light
    ///     Render each hull lit
    ///     Compute the final light (distort/shadow map and render shadow shaders)
    /// Apply blur 
    ///
    ////////////////////////////////////////////////////////////
    void                        ComputeLights();
    ////////////////////////////////////////////////////////////
    /// Render the lights
    ///
    ////////////////////////////////////////////////////////////
    void                        RenderLights();
    
    ////////////////////////////////////////////////////////////
    /// Add a light, if the light is already in the system, don't add it
    ///
    /// \param _pLight :        Light to add
    ///
    /// \return Whether the light was successfully added or not
    ///
    ////////////////////////////////////////////////////////////
    bool                        AddLight( Light* _pLight );
    ////////////////////////////////////////////////////////////
    /// Remove a light
    ///
    /// \param _pLight :        Light to remove
    /// \param _bDeleteLight :  Whether to delete the light after removing or not
    ///
    ////////////////////////////////////////////////////////////
    void                        RemoveLight( Light* _pLight, bool _bDeleteLight = true );
    
    ////////////////////////////////////////////////////////////
    /// Add a hull, if the hull is already in the system, don't add it
    ///
    /// \param _pHull :         Hull to add
    ///
    /// \return Whether the hull was successfully added or not
    ///
    ////////////////////////////////////////////////////////////
    bool                        AddHull( Hull* _pHull );
    ////////////////////////////////////////////////////////////
    /// Remove a hull
    ///
    /// \param _pHull :         Hull to remove
    /// \param _bDeleteHull :   Whether to delete the hull after removing or not
    ///
    ////////////////////////////////////////////////////////////
    void                        RemoveHull( Hull* _pHull, bool _bDeleteHull = true );
    
    ////////////////////////////////////////////////////////////
    /// Set the staticity of an occupant
    ///
    /// \param _pOccupant :     Occupant to change the staticity
    /// \param _bIsStatic :     Static or dynamic ?
    ///
    ////////////////////////////////////////////////////////////
    void                        SetStatic( QuadTreeOccupant* _pOccupant, bool _bIsStatic );
    
    /*********************** Accessors ************************/
    void                        SetView( const sf::View& _view );
    
    void                        SetAmbientColor( const sf::Color& _Color );
    const sf::Color&            GetAmbientColor( ) const;
    
    void                        SetAlphaThreshold( float _fAlphaThreshold );
    float                       GetAlphaThreshold() const;
    
    void                        SetBlurRadius( float _fBlurRadius );
    float                       GetBlurRadius() const;
    
    unsigned                    GetNumLights() const;
    unsigned                    GetNumHulls() const;

private:
    friend class                Light;
    
    ////////////////////////////////////////////////////////////
    /// Render the hulls for the given light
    ///
    /// \param _pLight :        Light casting shadows
    /// \param _pLightMatrix :  Light projection matrix
    /// \param _eHullsType :    Type of hulls to render
    ///
    /// \return The number of hulls rendered
    ///
    ////////////////////////////////////////////////////////////
    unsigned                    RenderHullsOnLight( Light* _pLight, const float* _pLightMatrix, QuadTreeOccupant::eType_Occupant _eHullsType = QuadTreeOccupant::eType_All );
    
    ////////////////////////////////////////////////////////////
    /// Compute the light's shadow
    /// Apply the distort/shadow map and render shadow shaders
    ///
    /// \param _pLight :        Light to render
    /// \param _uNumHulls :     Number of hulls rendered (if 0, skip the distort step)
    ///
    ////////////////////////////////////////////////////////////
    void                        ComputeLightShadow( Light* _pLight, unsigned _uNumHulls );
    
    /******************** Private members *********************/
    sf::Color                   m_AmbientColor;             /// Ambient color
    td_LightSet                 m_Lights;                   /// All lights
    QuadTree                    m_LightsTree;               /// Quad tree for the lights
    td_HullSet                  m_Hulls;                    /// All hulls
    QuadTree                    m_HullsTree;                /// Quad tree for the hulls

    /// Array of newly found static lights that will be rendered once at the next frame
    td_LightVec                 m_StaticLightsToCompute;

    sf::IntRect                 m_Viewport;                 /// Viewport
    math::AABB                  m_ViewAABB;                 /// AABB of the view
    sf::RenderWindow*           m_pRenderWindow;            /// Render window
    
    sf::View                    m_LightView;                /// View used when computing lights
    sf::View                    m_AlternateView;            /// Alternate view used when rendering lights

    sf::RenderTexture           m_FinalTexture;             /// Final texture to render
    sf::RenderTexture           m_LightTexture;             /// Temporary texture used for each light
    
    // Shadow map
    float                       m_fAlphaThreshold;          /// Alpha threshold when computing obstacles
    sf::RenderTexture           m_ShadowMapTex;             /// Texture of the shadow map
    sf::Shader                  m_ShadowMapShader;          /// Shader for the distortion and shadow mapping
    // Draw shadow
    sf::RenderTexture           m_DrawShadowsTex;           /// Texture for drawing shadows
    sf::Shader                  m_DrawShadowsShader;        /// Shader for drawing shadows
    // Blend Static/Dynamic
    sf::Shader                  m_BlendStaticDynamicShader; /// Shader for blending static light's texture with its dynamic part
    // Blur
    float                       m_fBlurRadius;              /// Radius for the blur
    sf::RenderTexture           m_HBlurTex;                 /// Texture for the horizontal blur
    sf::RenderTexture           m_VBlurTex;                 /// Texture for the vertical blur
    sf::Shader                  m_BlurShader;               /// Shader for the blur

#ifdef _DEBUG
public:
    void                        DebugRender( bool _bShowQuadTrees = false, const sf::Color& _Color = sf::Color::White );
#endif // _DEBUG
};

/************************** Inline ************************/
inline void LightSystem::SetAmbientColor( const sf::Color& _Color ) {
    m_AmbientColor = _Color;
}

inline const sf::Color& LightSystem::GetAmbientColor() const {
    return m_AmbientColor;
}

inline void LightSystem::SetAlphaThreshold( float _fAlphaThreshold ) {
    m_fAlphaThreshold = _fAlphaThreshold;
}

inline float LightSystem::GetAlphaThreshold() const {
    return m_fAlphaThreshold;
}

inline void LightSystem::SetBlurRadius( float _fBlurRadius ) {
    m_fBlurRadius = _fBlurRadius;
}

inline float LightSystem::GetBlurRadius() const {
    return m_fBlurRadius;
}
    
inline unsigned LightSystem::GetNumLights() const {
    return m_Lights.size();
}

inline unsigned LightSystem::GetNumHulls() const {
    return m_Hulls.size();
}

    } // namespace light
} // namespace smeg

#include <SmegTools/Debug/DebugNewOff.hpp>

#endif // LIGHT_SYSTEM_HPP
